﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
#if NETFX_CORE
using Windows.UI.Xaml;
#endif

namespace UnitDriven
{
	public class TypeTester : ObservableObject, IRunTests
	{
		private TestContext _context;
		private Type _type;
		private bool _isRunning;
		private bool _isExpanded;
		private ObservableCollection<MethodTester> _methods = new ObservableCollection<MethodTester>();

		public TestContext Context
		{
			get { return _context; }
		}

		public Type TestType
		{
			get { return _type; }
		}

		public bool IsRunning
		{
			get { return _isRunning; }
			private set
			{
				_isRunning = value;
				OnPropertyChanged("IsRunning");
				OnPropertyChanged("IsNotRunning");
				OnPropertyChanged("Status");
			}
		}

		public bool IsNotRunning
		{
			get { return !IsRunning; }
			set { IsRunning = !value; }
		}

		public Visibility IsVisible
		{
			get
			{
				return this.Items.Any(i => i.IsVisible == Visibility.Visible)
					? Visibility.Visible
					: Visibility.Collapsed;
			}
		}

		public string Name { get { return _type.Name; } }

		public ObservableCollection<MethodTester> Items
		{
			get { return _methods; }
		}

		public int Succeeded
		{
			get
			{
				var count = (from m in Items
							 where m.Status == TestResult.Success
							 select 1)
							 .Sum();

				return count;
			}
		}

		public bool IsExpanded
		{
			get { return _isExpanded; }
			set { _isExpanded = value; OnPropertyChanged("IsExpanded"); }
		}

		public TestResult Status
		{
			get
			{
				if (_methods.Any(item => item.Status == TestResult.Fail))
					return TestResult.Fail;
				if (_methods.Any(item => item.Status == TestResult.Evaluating))
					return TestResult.Evaluating;
				if (_methods.Any(item => item.Status == TestResult.Indeterminate))
					return TestResult.Indeterminate;

				return TestResult.Success;
			}
		}

		public TypeTester(TestContext context, Type type)
		{
			if (context == null)
				throw new ArgumentNullException("context");
			if (type == null)
				throw new ArgumentNullException("type");

#if NETFX_CORE
			if (!type.GetTypeInfo().IsDefined(typeof(TestClassAttribute), true))
				throw new ArgumentException("Type must have a TestClass attribute");
            if (!type.GetTypeInfo().IsPublic)
				throw new ArgumentException("Type must be public");
            if (type.GetTypeInfo().IsAbstract)
				throw new ArgumentException("Type must not be abstract");
            if (type.GetTypeInfo().IsInterface)
				throw new ArgumentException("Type must not be an interface");
#else
			if (!type.IsDefined(typeof(TestClassAttribute), true))
				throw new ArgumentException("Type must have a TestClass attribute");
			if (!type.IsPublic)
				throw new ArgumentException("Type must be public");
			if (type.IsAbstract)
				throw new ArgumentException("Type must not be abstract");
			if (type.IsInterface)
				throw new ArgumentException("Type must not be an interface");
#endif

            _context = context;
			_type = type;
			_context.PropertyChanged += Context_PropertyChanged;

            
#if NETFX_CORE
            var methods = _type.GetTypeInfo().DeclaredMethods;
#else
            var methods = _type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
#endif

			foreach (MethodInfo method in from m in methods
										  orderby m.Name ascending
										  select m)
			{
				if (method.IsPublic && !method.IsAbstract &&
					(method.IsDefined(typeof(TestMethodAttribute), true) || method.IsDefined(typeof(TestCaseAttribute), true)))
				{
					MethodTester tester = new MethodTester(this, method);
					tester.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(tester_PropertyChanged);
					_methods.Add(tester);
				}
			}
		}

		void Context_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "Filter")
			{
				this.IsExpanded = true;
				this.OnPropertyChanged("IsVisible");
			}
		}

		void tester_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "IsRunning")
				IsRunning = _methods.Any(m => m.IsRunning);
		}

		public void RunTests()
		{
			IsRunning = true;
			foreach (MethodTester tester in _methods.Where(m => m.IsVisible == Visibility.Visible))
				tester.RunTests();
		}
	}
}
